
#include <BRender>
#include <BViewer>
#include <BPainter>
#include <BKeyEvent>
#include <BMouseEvent>
#include <BStyle>
#include <BSystem>

#include "../Utilities.h"
#include "DrawLineStripeHandler.h"

DrawLineStripeHandler::DrawLineStripeHandler(BHandler* parent) : BHandler(parent)
{
	_vertexPicker = new AdsorbVertexPicker();
	_edgePicker = new EdgePicker();
	_drawNote = new DrawLineStripeNote();
	_drawNote->setProperty("picker", _vertexPicker);
	_drawNote->setProperty("handler", this);
	_transformer = new PrimitiveTransformer();
	_transformer->setEnabled(false);

	connect(this, Signal_AssetChanged, &DrawLineStripeHandler::slotAssetChanged);
	connect(this, Signal_PropertyChanged, &DrawLineStripeHandler::slotPropertyChanged);

	connect(_drawNote, "mode-changed", &DrawLineStripeHandler::slotModeChanged);
	connect(_transformer, Signal_Finished, &DrawLineStripeHandler::slotTransformFinished);
}
DrawLineStripeHandler::~DrawLineStripeHandler()
{

}

void DrawLineStripeHandler::changeOperaMode()
{
	_hoveredHit = 0;
	_hoveredIndex = -1;
	_selectedIndices.clear();
	_selectedPrimitive = Primitive_None;
	DrawLineStripeNote::OperaMode mode = _drawNote->operaMode();
	if (mode == DrawLineStripeNote::OMode_Edit)
	{
		applyChanges();
		_transformer->setEnabled(true);
	}
	else
	{
		revertPoints();
		_transformer->setEnabled(false);
	}
	this->fresh();
}
void DrawLineStripeHandler::revertPoints()
{
	Target* target = this->asset();
	_linePoints.reset();
	if (_stripeShape && _stripeShape->linePoints().size())
	{
		BMatrix  matrix = _stripeShape->matrix();
		if (BNode* node = target->node())
			matrix *= node->matrix();
		_linePoints.append(_stripeShape->linePoints());
		for (int i = 0; i < _linePoints.size(); i++)
			_linePoints[i] *= matrix;
	}
	_lineChanged = false;
}
void DrawLineStripeHandler::applyChanges()
{
	Target* target = this->asset();
	if (_lineChanged && target)
	{
		_lineChanged = false;
		BVectorArray vertices = _linePoints;
		if (BNode* node = target->node())
		{
			BMatrix matrix = node->matrix().inverse();
			for (int i = 0; i < vertices.size(); i++)
				vertices[i] *= matrix;
		}
		if (_stripeShape.empty())
		{
			_stripeShape = new LineStripe();
			if (BNode* node = target->node())
			{
				BString name = FindValidName(node, "stripe");
				_stripeShape->setName(name);
				node->insertShape(_stripeShape);
				target->reset(_stripeShape, node);
			}
			else if (BGroup* group = target->group())
			{
				BString name = FindValidName(group, "stripe");
				_stripeShape->setName(name);
				group->insert(_stripeShape);
				target->reset(_stripeShape, group);
			}
			else if (BScene* scene = target->scene())
			{
				BString name = FindValidName(scene, "stripe");
				_stripeShape->setName(name);
				scene->insert(_stripeShape);
				target->reset(_stripeShape);
			}
			BSpace space = vertices.first();
			for (int i = 1; i < vertices.size(); i++)
				space.expand(vertices[i]);
			_stripeShape->setPosition(space.center());
		}
		BMatrix matrix = _stripeShape->matrix().inverse();
		for (int i = 0; i < vertices.size(); i++)
			vertices[i] *= matrix;
		_stripeShape->setLinePoints(vertices);
		this->emit("object-changed", _stripeShape);
		this->post("target-changed", target);
	}
}
void DrawLineStripeHandler::resetChanges()
{
	_lineChanged = false;
}

bool DrawLineStripeHandler::collectEdges(const BVectorArray* vertices, const BElement* element, int index, EdgeArray& edges)
{
	edges.reset();
	const BIntArray* indices = element->indices();
	if (element->primitive() == Primitive_Triangles)
	{
		for (int i = 0; i < indices->size(); i += 3)
		{
			int ia = indices->at(i + 0);
			int ib = indices->at(i + 1);
			int ic = indices->at(i + 2);
			const BVector& va = vertices->at(ia);
			const BVector& vb = vertices->at(ib);
			const BVector& vc = vertices->at(ic);
			if (index == ia)
			{
				edges.append(FlatEdge(ia, ib, va, vb));
				edges.append(FlatEdge(ia, ic, va, vc));
				continue;
			}
			if (index == ib)
			{
				edges.append(FlatEdge(ib, ia, vb, va));
				edges.append(FlatEdge(ib, ic, vb, vc));
				continue;
			}
			if (index == ic)
			{
				edges.append(FlatEdge(ic, ia, vc, va));
				edges.append(FlatEdge(ic, ib, vc, vb));
				continue;
			}
		}
	}
	return edges.size();
}

void DrawLineStripeHandler::slotAssetChanged(BObject* object, const BValue& value)
{
	Target* target = value;
	this->setEnabled(target);
	_drawNote->setEnabled(target);
}
void DrawLineStripeHandler::slotPropertyChanged(BObject* object, const BValue& value)
{
	//if (Config* config = this->property("config"))
	//{
	//	_vertexPicker->setAdsorbPoint(config->adsorbPoint());
	//	_vertexPicker->setAdsorbLineMiddle(config->adsorbMiddle());
	//	_vertexPicker->setAdsorbPolygonCenter(config->adsorbCenter());
	//}
}

void DrawLineStripeHandler::slotModeChanged(BObject* object, const BValue& value)
{
	changeOperaMode();
}
void DrawLineStripeHandler::slotTransformFinished(BObject* object, const BValue& value)
{
	Target* target = this->asset();
	if (_stripeShape)
	{
		BMatrix  matrix = _stripeShape->matrix();
		if (BNode* node = target->node())
			matrix *= node->matrix();
		_linePoints = _stripeShape->linePoints();
		for (int i = 0; i < _linePoints.size(); i++)
			_linePoints[i] *= matrix;
		_lineChanged = false;
	}
}

void DrawLineStripeHandler::attached(BViewer* viewer)
{
	emit("apply-transformer", _transformer);
	emit("append-note", _drawNote);
	Target* target = this->asset();
	if (BShape* shape = target->shape())
	{
		_stripeShape = dynamic_cast<LineStripe*>(shape);
	}
	revertPoints();
	_vertexPicker->setCamera(this->camera());
	_edgePicker->setCamera(this->camera());
	if (const BStyle* style = this->viewer()->realStyle())
	{
		_dragImage = style->image("drag");
	}
}
void DrawLineStripeHandler::detached(BViewer* viewer)
{
	emit("remove-transformer", _transformer);
	emit("remove-note", _drawNote);
	applyChanges();
	_stripeShape = 0;
	_linePoints.clear();
	_vertexPicker->setCamera(0);
	_edgePicker->setCamera(0);
	_dragImage = 0;
	_hoveredHit = 0;
	_hoveredIndex = -1;
}

void DrawLineStripeHandler::paint(BPainter& painter)
{
	if (_hoveredHit && _dragging)
	{
		const BVector& vertex = _hoveredHit->absolutePoint();
		BPoint pos = this->viewer()->point(vertex);
		painter.drawImage(pos, _dragImage, Align_Center);
	}
}
void DrawLineStripeHandler::render(BRender& render)
{
	render.setLighting(false);
	render.setDepthTest(false);

	render.setLineStipple(0xF0F0);
	render.setColor(200, 225, 250);
	render.drawLineStrip(_linePoints);
	render.setLineStipple(0x0000);

	if (_drawNote->operaMode() == DrawLineStripeNote::OMode_Draw)
	{
		if (_linePoints.size())
		{
			render.setDepthOffset(-1);
			render.drawLineStrip(_linePoints);
			if (_hoveredHit)
			{
				const BVector& last = _linePoints.last();
				render.drawLine(last, _hoveredHit->absolutePoint());
			}
		}
	}
	if (_drawNote->operaMode() == DrawLineStripeNote::OMode_Edit)
	{
		if (_hoveredPrimitive == Primitive_Points)
		{
			if (_linePoints.check(_hoveredIndex))
			{
				render.setColor(200, 100, 0);
				render.setPointSize(6);
				render.drawVertex(_linePoints.at(_hoveredIndex));
			}
		}
		if (_hoveredPrimitive == Primitive_Lines)
		{
			if (_linePoints.check(_hoveredIndex) && _hoveredIndex < _linePoints.size() - 1)
			{
				const BVector& v0 = _linePoints.at(_hoveredIndex);
				const BVector& v1 = _linePoints.at(_hoveredIndex + 1);
				render.setColor(250, 150, 50);
				render.setLineWidth(3);
				render.drawLine(v0, v1);
			}
		}
		if (_selectedPrimitive == Primitive_Points)
		{
			for (int i = 0; i < _selectedIndices.size(); i++)
			{
				int index = _selectedIndices.at(i);
				const BVector& point = _linePoints.at(index);
				render.setColor(250, 50, 150);
				render.setPointSize(6);
				render.drawVertex(point);
			}
		}
		if (_selectedPrimitive == Primitive_Lines)
		{
			for(int i = 0; i < _selectedIndices.size(); i++)
			{
				int index = _selectedIndices[i];
				const BVector& v0 = _linePoints.at(index);
				const BVector& v1 = _linePoints.at(index + 1);
				render.setColor(250, 50, 150);
				render.setLineWidth(3);
				render.drawLine(v0, v1);
			}
		}
	}
	if (_drawNote->operaMode() == DrawLineStripeNote::OMode_Pick)
	{
		render.setDepthOffset(-1);
		if (_selectedHit && _selectedHit->primitive() == Primitive_Lines)
		{
			BLine line = _selectedHit->property("line");
			line *= _selectedHit->absoluteMatrix();
			render.setColor(150, 0, 150);
			render.drawLine(line);
		}
		if (_hoveredHit && _hoveredHit->primitive() == Primitive_Lines)
		{
			BLine line = _hoveredHit->property("line");
			line *= _hoveredHit->absoluteMatrix();
			render.setColor(250, 0, 250);
			render.drawLine(line);
		}
		render.setDepthOffset(0);
	}
	if (_hoveredHit)
	{
		const BPolygon& polygon = _hoveredHit->property("polygon");
		BMatrix matrix = _hoveredHit->absoluteMatrix();
		render.pushMatrix(matrix);
		render.setPolySmooth(true);
		render.drawPolygon(polygon);
		render.setPointSize(6);
		render.setPointSmooth(true);
		render.drawVertex(_hoveredHit->point());
		render.popMatrix();
	}
}

void DrawLineStripeHandler::updateEvent(const BEvent& event)
{

}
void DrawLineStripeHandler::styleEvent(const BEvent& event)
{
	if (const BStyle* style = event.value())
	{
		_dragImage = style->image("drag");
	}
}
void DrawLineStripeHandler::freshEvent(const BEvent& event)
{

}

void DrawLineStripeHandler::keyPressEvent(const BKeyEvent& keyEvent)
{
	if (keyEvent.key() == Key_Enter)
	{
		if (_linePoints.size())
			applyChanges();
		this->detach();
		keyEvent.accept();
		return;
	}
	if (keyEvent.key() == Key_Back)
	{
		_linePoints.pop();
		_lineChanged = true;
		this->emit(Signal_Changed);
		this->fresh();
		keyEvent.accept();
		return;
	}
	if (keyEvent.key() == Key_Delete)
	{
		_selectedIndices.sort(-1);
		for (int i = 0; i < _selectedIndices.size(); i++)
		{
			int index = _selectedIndices[i];
			_linePoints.remove((int)index);
		}
		this->fresh();
		keyEvent.accept();
		return;
	}
	if (keyEvent.key() == Key_Tab)
	{
		int operaMode = _drawNote->operaMode() + 1;
		if (operaMode >= DrawLineStripeNote::OMode_Max)
			operaMode = DrawLineStripeNote::OMode_Draw;
		_drawNote->setOperaMode((DrawLineStripeNote::OperaMode)operaMode);
		changeOperaMode();
		keyEvent.accept();
		return;
	}
	if (keyEvent.key() == Key_Space)
	{
		if (_selectedIndices.size())
		{
			BSpace space = _linePoints.at(_selectedIndices.at(0));
			if (_selectedPrimitive == Primitive_Points)
			{
				for (int i = 0; i < _selectedIndices.size(); i++)
				{
					int index = _selectedIndices.at(i);
					space.expand(_linePoints.at(index));
				}
			}
			if (_selectedPrimitive == Primitive_Lines)
			{
				for (int i = 0; i < _selectedIndices.size(); i++)
				{
					int index = _selectedIndices[i];
					space.expand(_linePoints.at(index));
					space.expand(_linePoints.at(index + 1));
				}
			}
			emit("focus", space);
			keyEvent.accept();
			return;
		}
	}
}
void DrawLineStripeHandler::keyReleaseEvent(const BKeyEvent& keyEvent)
{

}

void DrawLineStripeHandler::mouseEnterEvent(const BMouseEvent& mouseEvent)
{

}
void DrawLineStripeHandler::mouseLeaveEvent(const BMouseEvent& mouseEvent)
{

}
void DrawLineStripeHandler::mousePressEvent(const BMouseEvent& mouseEvent)
{
	if (mouseEvent.button() == Button_Left)
	{
		if (_drawNote->operaMode() == DrawLineStripeNote::OMode_Edit)
		{
			if (_selectedPrimitive == _hoveredPrimitive)
			{
				if (BSystem::KeyState(Key_Ctrl))
				{
					if (_selectedIndices.contain(_hoveredIndex))
						_selectedIndices.remove(_hoveredIndex, 1);
					else
						_selectedIndices.append(_hoveredIndex);
				}
				else
				{
					if (!_selectedIndices.contain(_hoveredIndex))
					{
						_selectedIndices.clear();
						_selectedIndices.append(_hoveredIndex);
					}
				}
			}
			else
			{
				_selectedPrimitive = _hoveredPrimitive;
				_selectedIndices.clear();
				_selectedIndices.append(_hoveredIndex);
			}
			_transformer->setPrimitive(_selectedPrimitive);
			_transformer->input("indices", _selectedIndices);
			_drawNote->setProperty("primitive", _selectedPrimitive);
			_drawNote->setProperty("selected", _selectedIndices);
			_drawNote->setProperty("vertices", _linePoints);
			_drawNote->updateButtonStatus();
			if (_selectedPrimitive == Primitive_Points && _selectedIndices.size() == 1)
			{
				_dragging = true;
			}
			this->fresh();
		}
		mouseEvent.accept();
	}
}
void DrawLineStripeHandler::mouseReleaseEvent(const BMouseEvent& mouseEvent)
{
	if (mouseEvent.button() == Button_Left)
	{
		if (_drawNote->operaMode() == DrawLineStripeNote::OMode_Edit)
		{
			_dragging = false;
		}
		this->fresh();
		mouseEvent.accept();
	}
}
void DrawLineStripeHandler::mouseMoveEvent(const BMouseEvent& mouseEvent)
{
	BCamera* camera = this->camera();
	const BPoint& pos = mouseEvent.pos();
	BLine line = this->viewer()->line(pos);
	Button button = mouseEvent.button();
	if (mouseEvent.button() == Button_None)
	{
		Target* target = this->asset();
		if (_drawNote->operaMode() == DrawLineStripeNote::OMode_Draw)
		{
			_vertexPicker->setLine(line);
			_vertexPicker->exclude(_stripeShape);
			BPickHit* hit = 0;
			if (BNode* node = target->node())
				hit = _vertexPicker->apply(node);
			else if (BGroup* group = target->group())
				hit = _vertexPicker->apply(group);
			else if (BShape* shape = target->shape())
				hit = _vertexPicker->apply(shape);
			_vertexPicker->include(_stripeShape);
			_hoveredHit = hit;
			if (_hoveredHit)
			{
				this->fresh();
			}
		}
		if (_drawNote->operaMode() == DrawLineStripeNote::OMode_Edit)
		{
			int index = -1;
			Primitive primitive = Primitive_None;
			for (int i = 0; i < _linePoints.size(); i++)
			{
				const BVector& point = _linePoints[i];
				BReal len = line.distance(point);
				BReal pixlen = len / camera->unit(point);
				if (pixlen < 10)
				{
					index = i;
					primitive = Primitive_Points;
					break;
				}
			}
			if (index < 0)
			{
				for (int i = 0; i < _linePoints.size() - 1; i++)
				{
					const BVector& p0 = _linePoints[i];
					const BVector& p1 = _linePoints[i + 1];
					BVector point = line.closest(BLine(p0, p1));
					BReal len = line.distance(point);
					BReal pixlen = len / camera->unit(point);
					if (pixlen < 10)
					{
						index = i;
						primitive = Primitive_Lines;
						break;
					}
				}
			}
			if (_hoveredIndex != index || _hoveredPrimitive != primitive)
			{
				_hoveredIndex = index;
				_hoveredPrimitive = primitive;
				this->fresh();
			}
		}
		if (_drawNote->operaMode() == DrawLineStripeNote::OMode_Pick)
		{
			_edgePicker->setLine(line);
			BPickHit* hit = 0;
			if (BNode* node = target->node())
				hit = _edgePicker->apply(node);
			else if (BGroup* group = target->group())
				hit = _edgePicker->apply(group);
			else if (BShape* shape = target->shape())
				hit = _edgePicker->apply(shape);
			_hoveredHit = hit;
			if (_hoveredHit)
			{
				this->fresh();
			}
		}
		mouseEvent.accept();
		return;
	}
	if (mouseEvent.button() == Button_Left)
	{
		Target* target = this->asset();
		if (_drawNote->operaMode() == DrawLineStripeNote::OMode_Edit)
		{
			if (_dragging)
			{
				_vertexPicker->setLine(line);
				_vertexPicker->exclude(_stripeShape);
				BPickHit* hit = 0;
				if (BNode* node = target->node())
					hit = _vertexPicker->apply(node);
				else if (BGroup* group = target->group())
					hit = _vertexPicker->apply(group);
				else if (BShape* shape = target->shape())
					hit = _vertexPicker->apply(shape);
				_vertexPicker->include(_stripeShape);
				_hoveredHit = hit;
				if (_hoveredHit)
				{
					this->fresh();
				}
				if (_hoveredHit && _linePoints.check(_hoveredIndex))
				{
					_linePoints[_hoveredIndex] = _hoveredHit->absolutePoint();
					_lineChanged = true;
					this->emit(Signal_Changed);
					this->fresh();
				}
			}
			mouseEvent.accept();
			return;
		}
	}
}

void DrawLineStripeHandler::mouseClickEvent(const BMouseEvent& mouseEvent)
{
	if (mouseEvent.button() == Button_Left)
	{
		if (_drawNote->operaMode() == DrawLineStripeNote::OMode_Draw)
		{
			if (_hoveredHit)
			{
				const BVector& point = _hoveredHit->absolutePoint();
				_linePoints.append(point);
				_lineChanged = true;
				this->emit(Signal_Changed);
				this->fresh();
			}
		}
		if (_drawNote->operaMode() == DrawLineStripeNote::OMode_Pick)
		{
			_selectedHit = _hoveredHit;
			if (_selectedHit)
			{
				const BLine& line = _selectedHit->property("line");
				_linePoints.clear();
				_linePoints.append(line.begin() * _selectedHit->absoluteMatrix());
				_linePoints.append(line.end() * _selectedHit->absoluteMatrix());
				EdgeArray edges;
				BMap<BReal, Edge> edgeMap;
				if (BGeometry* geometry = dynamic_cast<BGeometry*>(_selectedHit->shape()))
				{
					const BVectorArray* vertices = geometry->vertices();
					BElement* element = _selectedHit->element();
					if (const BIntArray* indices = element->indices())
					{
						int begin = _selectedHit->property("begin");
						int end = _selectedHit->property("end");
						int count = 0;
						while (collectEdges(vertices, element, end, edges))//forward
						{
							BVector direct = (vertices->at(end) - vertices->at(begin)).normal();
							edgeMap.reset();
							for (int e = 0; e < edges.size(); e++)
							{
								const Edge& edge = edges[e];
								const BVector& va = vertices->at(edge.a);
								const BVector& vb = vertices->at(edge.b);
								BVector normal = (vb - va).normal();
								BReal d = normal.x() * direct.x() + normal.y() * direct.y() + normal.z() * direct.z();
								if (d < 0)
									continue;
								BReal angle = normal.angle(direct);
								edgeMap[angle] = edge;
							}
							auto it = edgeMap.begin();
							BReal angle = it.key() * (180 / PI);
							if (angle > 10)
								break;
							const Edge& edge = it.value();
							_linePoints.append(vertices->at(edge.b) * _selectedHit->absoluteMatrix());
							begin = edge.a;
							end = edge.b;
							if (count++ > 100)
								break;
						}
					}
					if (const BIntArray* indices = element->indices())
					{
						int begin = _selectedHit->property("begin");
						int end = _selectedHit->property("end");
						int count = 0;
						while (collectEdges(vertices, element, begin, edges))
						{
							BVector direct = (vertices->at(begin) - vertices->at(end)).normal();
							edgeMap.reset();
							for (int e = 0; e < edges.size(); e++)
							{
								const Edge& edge = edges[e];
								const BVector& va = vertices->at(edge.a);
								const BVector& vb = vertices->at(edge.b);
								BVector normal = (vb - va).normal();
								BReal d = normal.x() * direct.x() + normal.y() * direct.y() + normal.z() * direct.z();
								if (d < 0)
									continue;
								BReal angle = normal.angle(direct);
								edgeMap[angle] = edge;
							}
							auto it = edgeMap.begin();
							BReal angle = it.key() * (180 / PI);
							if (angle > 10)
								break;
							const Edge& edge = it.value();
							const BVector& vb = vertices->at(edge.b);
							_linePoints.prepend(vb * _selectedHit->absoluteMatrix());
							begin = edge.b;
							end = edge.a;
							if (count++ > 100)
								break;
						}
					}
					_lineChanged = true;
				}
			}
			this->fresh();
		}
		mouseEvent.accept();
		return;
	}
}
